﻿using SBlogCore.Common.Attributes;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using System.Reflection;
using System.Reflection.Metadata;
using System.Reflection.PortableExecutable;
using System.Text;
using System.Threading.Tasks;

namespace SBlogCore.Common.Helpers
{
    [SkipScan]
    public static class ReflectionHelper
    {
        /// <summary>
        /// 
        /// </summary>
        static ReflectionHelper()
        {
            AllReferencedAssemblies = GetAllReferencedAssemblies();
            EffectiveTypes= AllReferencedAssemblies.SelectMany(u => u.GetTypes().Where(u => u.IsPublic&&!u.IsDefined(typeof(SkipScanAttribute),false)));
        }
        public static readonly IEnumerable<Assembly> AllReferencedAssemblies;
        public static readonly IEnumerable<Type> EffectiveTypes;
        /// <summary>
        /// 反射属性赋值
        /// </summary>
        /// <param name="model"></param>
        /// <param name="pairs"></param>
        public static void SetPropertieValue<TEntity>(this TEntity model, Dictionary<string, object> pairs) where TEntity :class
        {
            Type type = typeof(TEntity);
            PropertyInfo[] infos = type.GetProperties();
            foreach (var item in infos)
            {
                if (pairs.ContainsKey(item.Name))
                {
                    item.SetValue(model, pairs.GetValueOrDefault(item.Name));
                }
            }
        }
        /// <summary>
        /// 反射字段赋值
        /// </summary>
        /// <param name="model"></param>
        /// <param name="pairs"></param>
        public static void SeFieldValue<TEntity>(this TEntity model, Dictionary<string, object> pairs) where TEntity : class
        {
            Type type = typeof(TEntity);
            FieldInfo[] infos = type.GetFields();
            foreach (var item in infos)
            {
                if (pairs.ContainsKey(item.Name))
                {
                    item.SetValue(model, pairs.GetValueOrDefault(item.Name));
                }
            }
        }
        /// <summary>
        /// 判断是否为官方程序集
        /// </summary>
        /// <param name="asm"></param>
        /// <returns></returns>
        private static bool IsSystemAssembly(Assembly asm)
        {
            var asmCompanyAttr = asm.GetCustomAttribute<AssemblyCompanyAttribute>();
            if (asmCompanyAttr == null)
            {
                return false;
            }
            else
            {
                string companyName = asmCompanyAttr.Company;
                return companyName.Contains("Microsoft");
            }
        }
        private static bool IsSystemAssembly(string asmPath)
        {
            var moduleDef = AsmResolver.DotNet.ModuleDefinition.FromFile(asmPath);
            var assembly = moduleDef.Assembly;
            if (assembly == null)
            {
                return false;
            }
            var asmCompanyAttr = assembly.CustomAttributes.FirstOrDefault(c => c.Constructor?.DeclaringType?.FullName == typeof(AssemblyCompanyAttribute).FullName);
            if (asmCompanyAttr == null)
            {
                return false;
            }
            var companyName = ((AsmResolver.Utf8String?)asmCompanyAttr.Signature?.FixedArguments[0]?.Element)?.Value;
            if (companyName == null)
            {
                return false;
            }
            return companyName.Contains("Microsoft");
        }
        /// <summary>
        /// 获取当前程序集以及所引用的所有程序集
        /// </summary>
        /// <param name="skipSystemAssemblies">是否排除官方程序集</param>
        /// <returns></returns>
        public static IEnumerable<Assembly> GetAllReferencedAssemblies(bool skipSystemAssemblies = true)
        {
            //获取当前默认应用程序的可执行文件
            Assembly? rootAssembly = Assembly.GetEntryAssembly();
            if (rootAssembly == null)
            {
                rootAssembly = Assembly.GetCallingAssembly();
            }
            var returnAssemblies = new HashSet<Assembly>(new AssemblyEquality());
            var loadedAssemblies = new HashSet<string>();
            var assembliesToCheck = new Queue<Assembly>();
            assembliesToCheck.Enqueue(rootAssembly);
            if (skipSystemAssemblies && IsSystemAssembly(rootAssembly) != false)
            {
                if (IsValid(rootAssembly))
                {
                    returnAssemblies.Add(rootAssembly);
                }
            }
            while (assembliesToCheck.Any())
            {
                var assemblyToCheck = assembliesToCheck.Dequeue();
                foreach (var reference in assemblyToCheck.GetReferencedAssemblies())
                {
                    if (!loadedAssemblies.Contains(reference.FullName))
                    {
                        try
                        {
                            var assembly = Assembly.Load(reference);
                            if (skipSystemAssemblies && IsSystemAssembly(assembly))
                            {
                                continue;
                            }
                            assembliesToCheck.Enqueue(assembly);
                            loadedAssemblies.Add(reference.FullName);
                            if (IsValid(assembly))
                            {
                                returnAssemblies.Add(assembly);
                            }
                        }
                        catch (Exception)
                        {

                           
                        }
                    }
                }
            }
            var asmsInBaseDir = Directory.EnumerateFiles(AppContext.BaseDirectory,
                "*.dll", new EnumerationOptions { RecurseSubdirectories = true });
            foreach (var asmPath in asmsInBaseDir)
            {
                if (!IsManagedAssembly(asmPath))
                {
                    continue;
                }
                AssemblyName asmName = AssemblyName.GetAssemblyName(asmPath);
                //如果程序集已经加载过了就不再加载
                if (returnAssemblies.Any(x => AssemblyName.ReferenceMatchesDefinition(x.GetName(), asmName)))
                {
                    continue;
                }
                if (skipSystemAssemblies && IsSystemAssembly(asmPath))
                {
                    continue;
                }
                Assembly? asm = TryLoadAssembly(asmPath);
                if (asm == null)
                {
                    continue;
                }
                //Assembly asm = Assembly.Load(asmName);
                if (!IsValid(asm))
                {
                    continue;
                }
                if (skipSystemAssemblies && IsSystemAssembly(asm))
                {
                    continue;
                }
                returnAssemblies.Add(asm);
            }
            return returnAssemblies.ToArray();
        }
        /// <summary>
        /// 判断file这个文件是否是程序集
        /// </summary>
        /// <param name="file"></param>
        /// <returns></returns>
        private static bool IsManagedAssembly(string file)
        {
            using var fs = File.OpenRead(file);
            using PEReader peReader = new PEReader(fs);
            return peReader.HasMetadata && peReader.GetMetadataReader().IsAssembly;
        }
        private static bool IsValid(Assembly asm)
        {
            try
            {
                asm.GetTypes();
                asm.DefinedTypes.ToList();
                return true;
            }
            catch (ReflectionTypeLoadException)
            {
                return false;
            }
        }
        private static Assembly? TryLoadAssembly(string asmPath)
        {
            AssemblyName asmName = AssemblyName.GetAssemblyName(asmPath);
            Assembly? asm = null;
            try
            {
                asm = Assembly.Load(asmName);
            }
            catch (BadImageFormatException ex)
            {
                Debug.WriteLine(ex);
            }
            catch (FileLoadException ex)
            {
                Debug.WriteLine(ex);
            }

            if (asm == null)
            {
                try
                {
                    asm = Assembly.LoadFile(asmPath);
                }
                catch (BadImageFormatException ex)
                {
                    Debug.WriteLine(ex);
                }
                catch (FileLoadException ex)
                {
                    Debug.WriteLine(ex);
                }
            }
            return asm;
        }

    }
    class AssemblyEquality : EqualityComparer<Assembly>
    {
        public override bool Equals(Assembly? x, Assembly? y)
        {
            if (x == null && y == null) return true;
            if (x == null || y == null) return false;
            return AssemblyName.ReferenceMatchesDefinition(x.GetName(), y.GetName());
        }

        public override int GetHashCode([DisallowNull] Assembly obj)
        {
            return obj.GetName().FullName.GetHashCode();
        }
    }
}
